#include "tcpconnection.h"

namespace apollo
{
	namespace core
	{


		TcpConnection::TcpConnection(QSharedPointer<ConnectionConfig> config,
			QSharedPointer<WireFormat> format, QObject *parent) throw () 
			: Connection(parent), config(config), format(format)
		{
			Q_ASSERT(config != NULL && config->connectionName.isNull() == false && config->urls.size() > 0);
			//= 
		}

		TcpConnection::~TcpConnection(void)  throw ()
		{
			disconnect();
		}


		PayloadPtr TcpConnection::connect(PayloadPtr loginRequest)
		{

			socket.clear();
			QList<ConnectionConfig::UrlConfig * > failedUrls;
			const ConnectionConfig::UrlConfig* urlCfg = selectUrl(&failedUrls); 
			state.port = urlCfg->port;
			state.ip = urlCfg->ip;
			state.schema = urlCfg->schema;
			state.state = SessionState::Connecting;
			QLOG_INFO() << state.ip << ":" << state.port << "connecting";
			emit stateChanged(state);
			socket = QSharedPointer<QTcpSocket>(new QTcpSocket);

			socket->connectToHost(urlCfg->ip, urlCfg->port);
			if(socket->waitForConnected(config->connectingTimeout) == false)
			{				
				THROW_COMMUNICATION_EXP(urlCfg->ip, QString::number(urlCfg->port), socket->errorString());

			}
			state.state = SessionState::Connected;
			QLOG_INFO() << state.ip << ":" << state.port << "connected";
			emit stateChanged(state);

			state.state = SessionState::Logining;
			emit stateChanged(state);
			QLOG_INFO() << state.ip << ":" << state.port << "logining";
			format->write(socket.dynamicCast<QAbstractSocket>(), loginRequest);
			if(socket->waitForBytesWritten(config->connectingTimeout) == false)
			{
				THROW_COMMUNICATION_EXP(urlCfg->ip, QString::number(urlCfg->port), socket->errorString());
			}
			PayloadPtr response(new Payload);
			WireFormat::ReadState readstate = format->read(socket.dynamicCast<QAbstractSocket>(), response);
			QLOG_INFO() << state.ip << ":" << state.port << "received login response. reading state:" << readstate;
			if(readstate != WireFormat::Successful)
			{
				THROW_COMMUNICATION_EXP(urlCfg->ip, QString::number(urlCfg->port), QString::number(readstate));
			}

			QObject::connect(socket.data(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
			QObject::connect(socket.data(), SIGNAL(disconnected()), this, SLOT(onDisconnected()));
			QObject::connect(socket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
			QObject::connect(socket.data(), SIGNAL(connected()), this, SLOT(onConnected()));

			return response;


		}
		PayloadPtr TcpConnection::reconnect(PayloadPtr loginRequest)
		{
			QLOG_INFO() << "TcpConnection begin to re-connect";
			disconnect();
			return connect(loginRequest);
		}
		void TcpConnection::disconnect() throw()
		{

			if(socket.isNull() == false)
			{

				QLOG_INFO() << state.ip << ":" << state.port << "disconnecting";
				state.state = SessionState::Disconnecting;
				emit stateChanged(state);

				QObject::disconnect(socket.data(), SIGNAL(readyRead()), this, SLOT(onReadyRead()));
				QObject::disconnect(socket.data(), SIGNAL(disconnected()), this, SLOT(onDisconnected()));
				QObject::disconnect(socket.data(), SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
				QObject::disconnect(socket.data(), SIGNAL(connected()), this, SLOT(onConnected()));

				socket->disconnectFromHost();
				if( socket->state() != QAbstractSocket::UnconnectedState)
				{
					socket->waitForDisconnected(10000);
				}
				//socket.clear();
				state.state = SessionState::Disconnected;
				emit stateChanged(state);
				QLOG_INFO() << state.ip << ":" << state.port << "disconnected";
			}

		}

		void TcpConnection::write(PayloadPtr payload)
		{

			format->write(socket.dynamicCast<QAbstractSocket>(), payload);

		}
		bool TcpConnection::isReady() const
		{
			return (socket.isNull() == false && state.state == SessionState::Login);
		}

		//private method

		void TcpConnection::handleError(const QString &ip, const int port, const QAbstractSocket::SocketError error)
		{
			throw CommunicationException(__FILE__, __LINE__, "Fail to connect to " + ip + ":" + port + ", error: " + error);
		}

		const ConnectionConfig::UrlConfig* TcpConnection::selectUrl(QList<ConnectionConfig::UrlConfig*> *failedUrls) const
		{
			return &(config->urls.at(0));
		}


		//slots
		void TcpConnection::onReadyRead()
		{
			while(socket->isValid() && socket->bytesAvailable() > 0 )
			{
				PayloadPtr payload = format->unblockingRead(socket.dynamicCast<QAbstractSocket>(), readingCtx);
				if(readingCtx.state == WireFormat::Successful)
				{
					QLOG_TRACE() << "<< Received: id = " << payload->getMessageId() << "type:" 
						<< payload->getMessageType() << "header:" << payload->getHeader() << "Body:"
						<<  Utils::toHexRepresentation(payload->getBody(), payload->getBodySize());

					emit dataArrived(payload);

				}
				else if(readingCtx.state == WireFormat::MessageSizeError)
				{
					emit errorHappened(ConnectionErrorInfo(QString::number(readingCtx.state), "Message Size error.", ConnectionErrorInfo::ERROR_TYPE_MESSAGE));
					disconnect();
					break;
				}
				else if(readingCtx.state == WireFormat::ChecksumError)
				{

					emit errorHappened(ConnectionErrorInfo(QString::number(readingCtx.state), "check sum error.", ConnectionErrorInfo::ERROR_TYPE_MESSAGE));
					disconnect();
					break;
				}
				else if(readingCtx.state == WireFormat::NoAvailabeBytesError)
				{
					return;
				}
			}
		}
		void TcpConnection::onConnected()
		{		
			state.state = SessionState::Connected;
			QLOG_INFO() << state.ip << ":" << state.port << "connected";
			emit stateChanged(state);
		}
		void TcpConnection::onError(QAbstractSocket::SocketError error)
		{
			QLOG_ERROR() << state.ip << ":" << state.port << "error. code: " << error << ", desc: " << socket->errorString();
			emit errorHappened(ConnectionErrorInfo(QString::number(error), socket->errorString(), ConnectionErrorInfo::ERROR_TYPE_MESSAGE));
			disconnect();
		}
		void TcpConnection::onDisconnected()
		{
			state.state = SessionState::Disconnected;
			QLOG_INFO() << state.ip << ":" << state.port << "disconnected";
			emit stateChanged(state);
		}

	}
}